/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Estructuras;

/**
 *
 * @author jose
 */
//import java.util.Date;
import java.util.Observable;
import java.util.Observer;
import java.util.Iterator;

public class fair_sched_class extends sched_class implements Observer{

   private static long WMULT_SHIFT = 32;
   private static long WMULT_CONST = ~0L;

   private static long SCHED_LOAD_SHIFT = 10;
   private static long SCHED_LOAD_SCALE = 1L << SCHED_LOAD_SHIFT;

   private static long NICE_0_LOAD = SCHED_LOAD_SCALE;
    
   private Tree_RB<Long,Task_struct> rb;

   private long min_vruntime;

   private long clock;

   private cfs_rq cfs_rq;
   
   public fair_sched_class(Tree_RB tree){
      this.rb = tree;
      this.min_vruntime = 0;
      this.clock = 0;
      this.cfs_rq = new cfs_rq();
      
  }

   public fair_sched_class(){
      this.rb = new Tree_RB();
      this.min_vruntime = 0;
      this.clock = 0;
      this.cfs_rq = new cfs_rq();

  }

   //metodo que se ejecuta al momento de que el objeto observado cambie
  //o1 es el valor pasado por parametro en la this.notifyObservers
  public void update(Observable o, Object o1) {
     System.out.println("CPU recibe señal");
     //Actualiza el ciclo del reloj
     this.clock = (Long)o1;

  }

   private static long min_vruntime(long min_vruntime, long vruntime) {
       long delta = vruntime - min_vruntime;
       if (delta < 0){
           min_vruntime = vruntime;
       }
       return min_vruntime;
   }

   private static long max_vruntime(long min_vruntime, long vruntime){
       long delta = vruntime - min_vruntime;
       if (delta > 0){
           min_vruntime = vruntime;
       }
       return min_vruntime;
   }

   private void update_min_vruntime(){
       long vruntime = cfs_rq.min_vruntime;

       if (cfs_rq.curr != null) {
           vruntime = cfs_rq.curr.vruntime;
       }

       if (cfs_rq.rb_leftmost != null){
           sched_entity se = cfs_rq.rb_leftmost.getSE();

           if (cfs_rq.curr == null){
               vruntime = se.vruntime;
           } else {
               vruntime = min_vruntime(vruntime,se.vruntime);
           }
       }

       cfs_rq.min_vruntime = max_vruntime(cfs_rq.min_vruntime, vruntime);
   }

   private static long SRR(long x, long y){
       long f = 1L;
       return (((x) + 1L << ((y) - 1)) >> (y));
   }

   private static boolean unlikely(boolean x){
       return !x;
   }

   private static long calc_delta_mine(long delta_exec, long weight,
           load_weight lw){

       long tmp;

       if (lw.inv_weigth!=0){
           if (unlikely(lw.weight >= WMULT_CONST))
               lw.inv_weigth = 1;
           else
               lw.inv_weigth = 1 + (WMULT_CONST - lw.weight/2)
                       / (lw.weight +1);
       }

       tmp = delta_exec * weight;

       if (unlikely(tmp > WMULT_CONST))
           tmp = SRR(SRR(tmp,WMULT_SHIFT/2) * lw.inv_weigth, WMULT_SHIFT/2);
       else
           tmp = SRR(tmp * lw.inv_weigth, WMULT_SHIFT);

       return Math.min(tmp,Long.MAX_VALUE);

   }

   private static long calc_delta_fair(long delta, sched_entity se) {

       if (unlikely(se.load.weight != NICE_0_LOAD))
           delta = calc_delta_mine(delta,NICE_0_LOAD,se.load);

       return delta;
   }

   private void __update_curr(sched_entity curr, long delta_exec) {

       long delta_exec_weighted;
       curr.exec_max = Math.max(delta_exec, curr.exec_max);
       delta_exec_weighted = calc_delta_fair(delta_exec,curr);
       curr.vruntime += delta_exec_weighted;
       update_min_vruntime();
       

       
   }
   
   private void update_curr(){
       sched_entity curr = cfs_rq.curr;
       if (curr==null)
           return;
       long now = this.clock;//(new Date()).getTime(); //Pendiente, cambiar
       long delta_exec = now - curr.exec_start;
       if (delta_exec <= 0)
           return;
       __update_curr(curr,delta_exec);
       curr.exec_start = now;


   }

   private void update_stats_wait_start(sched_entity se){

       se.setWait_start(this.clock);
   }

   private void update_stats_wait_end(sched_entity se){
       long delta = this.clock - se.getWait_start();
       se.setWait_max(Math.max(se.getWait_max(),delta));

       se.increase_wait_count();

       se.increase_wait_sum(delta);

       se.setWait_start(0);
   }

   /*Metodo que agrega el proceso al arbol

    * @param just_wakeup: indica si el proceso se acaba de despertar
    */
   private void enqueue_entity(sched_entity se){

       //Si el proceso no acaba de despertar
       se.vruntime += this.cfs_rq.min_vruntime;

       //Actualizar
       update_curr();

       /* Si el proceso acaba de despertar, resetear ese campo
        *
        */
   }

   /*Metodo que agrega el proceso al arbol

    * @param just_wakeup: indica si el proceso se acaba de despertar
    */
   private void enqueue_entity(Task_struct task){

       sched_entity se = task.getSE();

       //Si el proceso no acaba de despertar
       se.vruntime += this.cfs_rq.min_vruntime;

       //Actualizar
       update_curr();

       /* Si el proceso acaba de despertar, resetear ese campo
        *
        */
       this.update_stats_wait_start(se);
       if (se!= cfs_rq.curr)
            rb.add(se.vruntime, task);
   }



  /* funcion llamada cuando una tarea cambia a estado runnable
   * este coloca el scheduling_entity (proceso) dentro del scheduler_class
   */
  public void enqueue_task(Task_struct task){
      System.out.println("\n\nenqueue_task "+task.getPid()+"\n\n");

      //Actualiza el vruntime


      //Inserta en el arbolhttps://ci4821-cpu.googlecode.com/svn

      //rb.add(task.getSE().vruntime, task);
      this.enqueue_entity(task);

      //incrementa la variable nr_running
      this.cfs_rq.increase_nr_running();

      imprimirContenido();
  }


   /**
    *Cuando una tarea no puede seguir ejecutandose esta funcion es llamada
    * para guardar el schedling_entity fuera del scheduler
    * esta decrementa la variable nr_runningresched;
    */
  public  Task_struct dequeue_task(Task_struct ts){

      sched_entity se = ts.getSE();
      //Codigo de dequeue_entity
      update_curr();
      if (se != cfs_rq.curr){
          this.rb.remove(se.vruntime);
      }
      update_min_vruntime();

      /* sleep indica si se saca al proceso del arbol para
       * ponerlo a dormir (bloquearlo). Deberia pasarse como
       * parametro.
      if (!sleep){
          se.vruntime -= cfs_rq.min_vruntime;
      }
       *
       */
      

      //Decrementa el nr_running
      this.cfs_rq.decrease_nr_running();

      return null;
  }



  /**
  *Agrega un procesos a la cola de expirados
  *
  */
  public void yield_task(Task_struct task){
      
      sched_entity se = task.getSE();
      
      if (cfs_rq.nr_running == 1)
          return;

      Task_struct rightmost = this.rb.getMajor();
      sched_entity rightmost_se = rightmost.getSE();

      this.rb.delete(se.getVruntime());

      update_curr();

      long vruntime = rightmost_se.getVruntime() +1;

      se.setVruntime(vruntime);

      this.rb.add(vruntime, task);
           
  }



  /**
   * Agrega un proceso a la cola de expropiados
   */
   public void agregarExpropiado(Task_struct task){

   }


  /**
   * Esta funcion revisa si una tarea en estado runnable con mayor prioridad
   * esta en el scheduler y de esta forma seleccionarla para que
   *se ejecute
   */
  /*public void check_preempt_curr(Task_struct ts){
    

  }*/


 /**
  *Funcion que selecciona la tarea mas apropiada para ser ejecutada
  */
   public  Task_struct pick_next_task(){


       if (cfs_rq.nr_running == 0){
           return null;
       }
       //Se obtiene el proceso con el menor vruntime

       Task_struct next = this.rb.getMinor();
       System.out.println("\n\nkkkpick_next_task "+next.getPid()+"\nhhh\n");
       imprimirContenido();
       sched_entity se = next.getSE();
       se.setExecStart(clock);
       this.update_stats_wait_end(se);
       this.cfs_rq.setCurr(se);

       return next;

   }

   public void imprimirContenido(){
        Iterator it = this.rb.keySet().iterator();
        System.out.println("Size-tree: "+this.rb.size());
        while(it.hasNext()){
            long key = (Long)it.next();
            Task_struct t = (Task_struct)this.rb.get(key);
            System.out.println("PID: "+t.getPid()+" "+t.getSE().vruntime + " "+key);
        }
        long key = this.rb.getMinor_key();
        Task_struct t = this.rb.getMinor_value();
        System.out.println("PID: "+t.getPid()+" "+t.getSE().vruntime + " "+key);
    }




}
